/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.misc;

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;


/**
 * A conversion of a collection.  The collection has its elements 
 * transformed by a given converter.  The converter may also opt 
 * to eliminate objects from the original collection; the conversion
 * may be smaller than the original collection.  Changes to the
 * original collection are automatically reflected in the conversion.
 * 
 * <p>The point is to extract only certain subclasses from a 
 * collection of some superclass type.
 * 
 * <p>Note that a conversion can never contain the null element.
 * 
 * <p>None of the optional Collection operators are supported by this
 * class; a Conversion is read-only.
 *
 * @param <Original>  the type of the original collection
 * @param <Converted>  the type of the converted subcollection
 */
public class Conversion<Original,Converted> extends AbstractCollection<Converted> {

	
	/**
	 * The collection whose elements to filter.
	 */
	private Collection<Original> collection;
	
	
	/**
	 * The converter to use for those elements.
	 */
	private Converter<Original,Converted> converter;
	
	
	/**
	 * Constructs a new conversion.
	 * 
	 * @param collection  the original collection to convert
	 * @param converter   the converter to use
	 */
	public Conversion(Collection<Original> collection, Converter<Original,Converted> converter) {
		if (collection == null) {
			throw new IllegalArgumentException("collection may not be null");
		}
		if (converter == null) {
			throw new IllegalArgumentException("converter may not be null");
		}
		this.collection = collection;
		this.converter = converter;
	}


	/**
	 * Returns the size of the collection
	 * 
	 * @return  the size of the collection
	 */
	public int size() {
		// Why is this not the default implementation?  grrr
		int count = 0;
		Iterator<Converted> iter = iterator();
		while (iter.hasNext()) {
			iter.next();
			count++;
		}
		return count;
	}	
	
	
	/**
	 * Returns an iterator over the conversion's elements.
	 * Only elements from the original collection that survive the
	 * conversion process will be included in the returned iterator.
	 * 
	 * @return  an iterator over the conversion's elements
	 */
	public Iterator<Converted> iterator() {
		return new ConvertingIterator<Original,Converted>(collection.iterator(), converter);
	}
	
	
	/**
	 * Returns a conversion that contains only instances of the given class or one of its
	 * subclasses.  Otherwise the instances are left alone.
	 * 
	 * @param <Super>  the type of the original collection
	 * @param <Sub>    the subclass we're interested in
	 * @param c        the original collection
	 * @param sub      the subclass we're interested in
	 * @return   a collection containing only the elements of that subclass
	 */
	public static <Super,Sub> Conversion<Super,Sub> subclasses(Collection<Super> c, final Class<Sub> sub) {
		Converter<Super,Sub> converter = new Converter<Super,Sub>() {
			@SuppressWarnings("unchecked")
			public Sub convert(Super object) {
				if (sub.isAssignableFrom(object.getClass())) {
					return (Sub)object;
				} else {
					return null;
				}
			}
		};
		return new Conversion<Super,Sub>(c, converter);
	}
	
}


class ConvertingIterator<Original,Converted> implements Iterator<Converted> {
	
	
	private Iterator<Original> iterator;
	private Converter<Original,Converted> converter;
	private Converted next;
	
	
	public ConvertingIterator(Iterator<Original> iterator, Converter<Original,Converted> converter) {
		this.iterator = iterator;
		this.converter = converter;
	}
	
	
	public boolean hasNext() {
		if (next != null) {
			return true;
		}
		while (iterator.hasNext()) {
			next = converter.convert(iterator.next());
			if (next != null) {
				return true;
			}
		}
		return false;
	}
	
	
	public Converted next() {
		if (!hasNext()) {
			throw new IllegalStateException();
		}
		Converted result = next;
		next = null;
		return result;
	}
	
	
	public void remove() {
		throw new UnsupportedOperationException();
	}

}